//========================================================================================================
//
//  File:      ASLoadTest.cs
//  Summary:   A User-friendly, Windows Form-based interface for load testing Analysis Servcies queries
//  Authors:   Rob Kerr (rkerr@blue-granite.com)
//  Date:	   March 12, 2011
//
//  Change history:
//    March 2011: rkerr    : Initial creation of application
//
// @TODO: (1) add support for cube processing performance analysis using XMLA input
// @TODO: (2) better memory management when the monitoring bench is run for extremely long periods of time
//
//---------------------------------------------------------------------
//
//  Copyright (C) 2011 BlueGranite.  All rights reserved.
//
// This source code is intended as a sample of how to make SSAS performance testing
// more user-friendly.  
//
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF 
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
//========================================================================================================
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Management;
using System.Diagnostics;
using Visifire.Charts;
using System.Windows.Media;
using System.Threading;
using System.Data.OleDb;
using System.Collections.Generic;
using System.IO;
using Microsoft.AnalysisServices;
using Microsoft.AnalysisServices.AdomdClient;
using System.Text;


namespace ASWorkbench
{
	/// <summary>
	/// Summary description for QueryLoadForm.
	/// </summary>
	public class ASLoadTest : System.Windows.Forms.Form
	{
        Chart _MemoryChart;
        bool _Connected = false;
        int _ThinkTimeLowSec = 0;
        int _ThinkTimeHighSec = 0;
        bool _TestCanceled = false;
        string _serverName = null;
        string _instanceName = null;
        Stopwatch _swOverallRuntime = new Stopwatch();
        FolderBrowserDialog _fbd;
        public int _ActiveQueries;
        public Object _lockActiveQueries = new Object();

        System.Windows.Forms.Timer Clock;               // this clock is used to collect data every second
        System.Windows.Forms.Timer PostRunClock;        // this clock is used to let data collection run for 5 seconds after the end of a test run, then stops the Clock

        ArrayList _QueryList = new ArrayList();              // array of strings.  One string per query to load test
        ArrayList _CounterDataList = new ArrayList();
        ArrayList _QueryWorkers = new ArrayList();

        private System.Windows.Forms.Integration.ElementHost MemoryChartHost;
        private DataGridView QuerySummaryTable;
        private DataGridView CounterTable;
        private Panel panel1;
        private ComboBox cmbCounterSet;
        private Label label3;
        private Button btnStartCounters;
        private TextBox txtServerName;
        private Label label1;
        private Label label5;
        private Panel panelRunTest;
        private Label label4;
        private TextBox txtQueryFilesPath;
        private ComboBox cmbTestProfile;
        private Label label8;
        private TextBox txtConcurrentThreads;
        private Label label6;
        private Label label7;
        private Button btnStartTest;
        private Label label9;
        private Panel panelSaveReport;
        private Button btnSaveReport;
        private Label label12;
        private TextBox txtReportPath;
        private Button btnBrowseForQueryFilesPath;
        private Button btnBrowseForReportPath;
        private ComboBox cmbDatabaseName;
        private Label label10;
        private Button btnStopTest;
        private Button btnStopCounters;
        private ComboBox cmbSQLVersion;
        private Label label2;
        private TextBox txtThinkTimeLow;
        private Label label11;
        private Label label13;
        private TextBox txtReportTitle;
        private CheckBox ckShowQueryEnds;
        private Button btnClearCache;
        private TextBox txtAutoStopTimer;
        private Label label14;
        private Label label16;
        private Label label15;
        private TextBox txtThinkTimeHigh;

        /// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

        //*************************************************************************************************
        //
        //          Function: QueryLoadForm
        //       Description: Constructor for this class
        //
        //*************************************************************************************************
        public ASLoadTest()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

            //-------------- Splash screen ----------------
            Splash splash = new Splash();
            splash.Show();
            Thread.Sleep(5000);


            _fbd = new FolderBrowserDialog();
            cmbSQLVersion.SelectedIndex = 0;
            cmbCounterSet.SelectedIndex = 0;
            cmbTestProfile.SelectedIndex = 0;
            txtConcurrentThreads.Text = "1";
            txtQueryFilesPath.Text = "C:\\ASWorkbench\\Queries";
            txtReportPath.Text = "C:\\ASWorkbench\\ReportOutput";

            //-----------  Initialze the data source for the query summary table so it isn't empty  ------------
            ArrayList ar = new ArrayList();
            QuerySummaryTable.DataSource = ar;

            //-----------  Initialze the data source for the counter table so it isn't empty  ------------
            ar = new ArrayList();
            CounterTable.DataSource = ar;

            //-------------  Start the timer used to update performance counters  ---------------
            Clock = new System.Windows.Forms.Timer();
            Clock.Interval = 1000;
            Clock.Tick += new EventHandler(Timer_Tick);


            btnStopCounters.Enabled = false;
        }


        //*************************************************************************************************
        //
        //          Function: Create_CounterDataList
        //       Description: Creates the list of counter objects that will be monitored in the chart and table
        //
        //*************************************************************************************************
        private void Create_CounterDataList()
        {
            string SSAS_CounterPrefix;

            //----------  SSAS registers default instance counters differently than named instance counters  --------
            if (_instanceName == null || _instanceName.Length == 0)
            {
                if (cmbSQLVersion.Text.CompareTo("Analysis Services 2008") == 0)
                    SSAS_CounterPrefix = "MSAS 2008";
                else
                    SSAS_CounterPrefix = "MSAS 2005";
            }
            else
            {
                SSAS_CounterPrefix = "MSOLAP$" + _instanceName;
            }

            //-----------  Clear the counter data list if it has items --------
            _CounterDataList.Clear();

            if (cmbCounterSet.Text.CompareTo("Query Performance") == 0)
            {
                _CounterDataList.Add(new CounterData(_serverName, "Processor", "% Processor Time", "_Total", Colors.Red, LineStyles.Solid, 1.5, AxisTypes.Secondary, RenderAs.Line, "N2", true, 1));
                _CounterDataList.Add(new CounterData(_serverName, "Memory", "Pages/sec", null, Colors.CornflowerBlue, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0", false, 1));
                _CounterDataList.Add(new CounterData(_serverName, "Memory", "Available bytes", null, Colors.DarkMagenta, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0", false, (float)0.001));
                _CounterDataList.Add(new CounterData(_serverName, "PhysicalDisk", "Current Disk Queue Length", "_Total", Colors.BlueViolet, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0", true, 1));

                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Memory Usage KB", null, Colors.Black, LineStyles.Solid, 1.5, AxisTypes.Primary, RenderAs.Line, "N0", false, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Memory Limit High KB", null, Colors.Red, LineStyles.Solid, 1.5, AxisTypes.Primary, RenderAs.Line, "N0", false, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Memory Limit Low KB", null, Colors.Yellow, LineStyles.Solid, 1.5, AxisTypes.Primary, RenderAs.Line, "N0", false, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Cleaner Memory shrinkable KB", null, Colors.RoyalBlue, LineStyles.Dotted, 2, AxisTypes.Primary, RenderAs.Line, "N0", true, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Cleaner Memory nonshrinkable KB", null, Colors.Cyan, LineStyles.Dotted, 2, AxisTypes.Primary, RenderAs.Line, "N0", true, 1));

                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Query pool busy threads", null, Colors.DarkOrange, LineStyles.Dotted, 2, AxisTypes.Secondary, RenderAs.Line, "N0", true, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Query pool idle threads", null, Colors.DarkGreen, LineStyles.Dotted, 2, AxisTypes.Secondary, RenderAs.Line, "N0", true, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Query pool job queue length", null, Colors.Blue, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0", true, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Connection", "Current Connections", null, Colors.Black, LineStyles.Dotted, 2, AxisTypes.Secondary, RenderAs.Line, "N0", true, 1));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Connection", "Requests/sec", null, Colors.DeepPink, LineStyles.Solid, 1, AxisTypes.Secondary, RenderAs.Line, "N0", true, 1));
            }
/*  FUTURE: Will add Processing Analysis to possible tests & reports
            else
            {
                _CounterDataList.Add(new CounterData(_serverName, "Processor", "% Processor Time", "_Total", Colors.DarkBlue, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N2"));
                _CounterDataList.Add(new CounterData(_serverName, "Memory", "Pages/sec", null, Colors.CornflowerBlue, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, "Memory", "Available bytes", null, Colors.DarkMagenta, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, "PhysicalDisk", "Current Disk Queue Length", "_Total", Colors.BlueViolet, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0"));

                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Processing Pool Busy Threads", null, Colors.DarkGreen, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Processing Pool Idle Threads", null, Colors.DarkRed, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Processing Pool Job Queue Length", null, Colors.Orange, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Threads", "Processing Pool Job Rate", null, Colors.DarkViolet, LineStyles.Solid, 0.5, AxisTypes.Secondary, RenderAs.Line, "N0"));

                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Memory Usage KB", null, Colors.DarkCyan, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Memory Limit High KB", null, Colors.DarkMagenta, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Memory Limit Low KB", null, Colors.DarkSlateGray, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Cleaner Memory shrinkable KB", null, Colors.Blue, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0"));
                _CounterDataList.Add(new CounterData(_serverName, SSAS_CounterPrefix + ":Memory", "Cleaner Memory nonshrinkable KB", null, Colors.DeepPink, LineStyles.Solid, 0.5, AxisTypes.Primary, RenderAs.Line, "N0"));
            }
 */ 
            //-------------  Turn on the test panel  -------------
            panelRunTest.Enabled = true;
        }

        //*************************************************************************************************
        //
        //          Function: Timer_Tick
        //       Description: Event handler called when the timer is fired
        //
        //*************************************************************************************************
        public void Timer_Tick(object sender, EventArgs eArgs)
        {
            if (_Connected && sender == Clock)
                UpdateChart();
             
            if (panelRunTest.Enabled && !btnStartTest.Enabled && !IsTestRunning() )
            {
                //-------------  Record the overall stop time ---------------
                _swOverallRuntime.Stop();

                //---------  Close the database connections -------------
                foreach (QueryThread t in _QueryWorkers)
                {
                    t.conn.Close();
                }

                //------------  Adjust the UI -------------------
                btnStartTest.Enabled = true;
                btnStopTest.Enabled = false;
                long ms = _swOverallRuntime.ElapsedMilliseconds / 1000;
                panelSaveReport.Enabled = true;

                //-------------- Kickoff thread to stop the timer collection -------------
                int autoStopCounterSec;
                bool isInt = int.TryParse(txtAutoStopTimer.Text.Trim(), out autoStopCounterSec);
                if (isInt)
                {
                    PostRunClock = new System.Windows.Forms.Timer();
                    PostRunClock.Tick += new EventHandler(PostRunClock_Tick);
                    PostRunClock.Interval = autoStopCounterSec * 1000;
                    PostRunClock.Start();
                }

                MessageBox.Show("Queued queries have completed.  Overall job run time: " + ms.ToString("N0") + " seconds.");
                
            }
        }

        //*************************************************************************************************
        //
        //          Function: PostRunClock_Tick
        //       Description: Is called after the end of a run.  When fired, it turns off data collection
        //
        //*************************************************************************************************
        public void PostRunClock_Tick(object sender, EventArgs eArgs)
        {
            if (sender == PostRunClock && btnStopCounters.Enabled)
            {
                Clock.Stop();
                btnStopCounters.Enabled = false;
                btnStartCounters.Enabled = true;
                PostRunClock.Stop();  // stop sending messages
            }
        }

        //*************************************************************************************************
        //
        //          Function: GetTime
        //       Description: Make a formatted string from the current time
        //
        //*************************************************************************************************
        public static string GetTime()
        {
            string TimeInString = "";
            int hour = DateTime.Now.Hour;
            int min = DateTime.Now.Minute;
            int sec = DateTime.Now.Second;

            TimeInString = (hour < 10) ? "0" + hour.ToString() : hour.ToString();
            TimeInString += ":" + ((min < 10) ? "0" + min.ToString() : min.ToString());
            TimeInString += ":" + ((sec < 10) ? "0" + sec.ToString() : sec.ToString());
            return TimeInString;
        }

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new ASLoadTest());
		}
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}

			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ASLoadTest));
            this.MemoryChartHost = new System.Windows.Forms.Integration.ElementHost();
            this.QuerySummaryTable = new System.Windows.Forms.DataGridView();
            this.CounterTable = new System.Windows.Forms.DataGridView();
            this.panel1 = new System.Windows.Forms.Panel();
            this.cmbSQLVersion = new System.Windows.Forms.ComboBox();
            this.label2 = new System.Windows.Forms.Label();
            this.btnStopCounters = new System.Windows.Forms.Button();
            this.cmbDatabaseName = new System.Windows.Forms.ComboBox();
            this.label10 = new System.Windows.Forms.Label();
            this.cmbCounterSet = new System.Windows.Forms.ComboBox();
            this.label3 = new System.Windows.Forms.Label();
            this.btnStartCounters = new System.Windows.Forms.Button();
            this.txtServerName = new System.Windows.Forms.TextBox();
            this.label1 = new System.Windows.Forms.Label();
            this.label5 = new System.Windows.Forms.Label();
            this.panelRunTest = new System.Windows.Forms.Panel();
            this.txtAutoStopTimer = new System.Windows.Forms.TextBox();
            this.label14 = new System.Windows.Forms.Label();
            this.btnClearCache = new System.Windows.Forms.Button();
            this.ckShowQueryEnds = new System.Windows.Forms.CheckBox();
            this.txtThinkTimeLow = new System.Windows.Forms.TextBox();
            this.label11 = new System.Windows.Forms.Label();
            this.btnStopTest = new System.Windows.Forms.Button();
            this.btnBrowseForQueryFilesPath = new System.Windows.Forms.Button();
            this.btnStartTest = new System.Windows.Forms.Button();
            this.cmbTestProfile = new System.Windows.Forms.ComboBox();
            this.label8 = new System.Windows.Forms.Label();
            this.txtConcurrentThreads = new System.Windows.Forms.TextBox();
            this.label6 = new System.Windows.Forms.Label();
            this.label4 = new System.Windows.Forms.Label();
            this.txtQueryFilesPath = new System.Windows.Forms.TextBox();
            this.label7 = new System.Windows.Forms.Label();
            this.label9 = new System.Windows.Forms.Label();
            this.panelSaveReport = new System.Windows.Forms.Panel();
            this.label13 = new System.Windows.Forms.Label();
            this.txtReportTitle = new System.Windows.Forms.TextBox();
            this.btnBrowseForReportPath = new System.Windows.Forms.Button();
            this.btnSaveReport = new System.Windows.Forms.Button();
            this.label12 = new System.Windows.Forms.Label();
            this.txtReportPath = new System.Windows.Forms.TextBox();
            this.txtThinkTimeHigh = new System.Windows.Forms.TextBox();
            this.label15 = new System.Windows.Forms.Label();
            this.label16 = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.QuerySummaryTable)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.CounterTable)).BeginInit();
            this.panel1.SuspendLayout();
            this.panelRunTest.SuspendLayout();
            this.panelSaveReport.SuspendLayout();
            this.SuspendLayout();
            // 
            // MemoryChartHost
            // 
            this.MemoryChartHost.Location = new System.Drawing.Point(8, 152);
            this.MemoryChartHost.Name = "MemoryChartHost";
            this.MemoryChartHost.Size = new System.Drawing.Size(1098, 303);
            this.MemoryChartHost.TabIndex = 13;
            this.MemoryChartHost.Text = "elementHost1";
            this.MemoryChartHost.Child = null;
            // 
            // QuerySummaryTable
            // 
            this.QuerySummaryTable.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.QuerySummaryTable.Location = new System.Drawing.Point(12, 664);
            this.QuerySummaryTable.Name = "QuerySummaryTable";
            this.QuerySummaryTable.Size = new System.Drawing.Size(1098, 125);
            this.QuerySummaryTable.TabIndex = 16;
            this.QuerySummaryTable.Visible = false;
            // 
            // CounterTable
            // 
            this.CounterTable.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
            this.CounterTable.Location = new System.Drawing.Point(12, 461);
            this.CounterTable.Name = "CounterTable";
            this.CounterTable.Size = new System.Drawing.Size(1098, 328);
            this.CounterTable.TabIndex = 17;
            this.CounterTable.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.CounterTable_CellContentClick);
            // 
            // panel1
            // 
            this.panel1.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.panel1.Controls.Add(this.cmbSQLVersion);
            this.panel1.Controls.Add(this.label2);
            this.panel1.Controls.Add(this.btnStopCounters);
            this.panel1.Controls.Add(this.cmbDatabaseName);
            this.panel1.Controls.Add(this.label10);
            this.panel1.Controls.Add(this.cmbCounterSet);
            this.panel1.Controls.Add(this.label3);
            this.panel1.Controls.Add(this.btnStartCounters);
            this.panel1.Controls.Add(this.txtServerName);
            this.panel1.Controls.Add(this.label1);
            this.panel1.Location = new System.Drawing.Point(12, 20);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(330, 128);
            this.panel1.TabIndex = 29;
            // 
            // cmbSQLVersion
            // 
            this.cmbSQLVersion.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cmbSQLVersion.FormattingEnabled = true;
            this.cmbSQLVersion.Items.AddRange(new object[] {
            "Analysis Services 2008",
            "Analysis Services 2005"});
            this.cmbSQLVersion.Location = new System.Drawing.Point(125, 71);
            this.cmbSQLVersion.Name = "cmbSQLVersion";
            this.cmbSQLVersion.Size = new System.Drawing.Size(192, 21);
            this.cmbSQLVersion.TabIndex = 3;
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(8, 74);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(82, 13);
            this.label2.TabIndex = 39;
            this.label2.Text = "Product Version";
            // 
            // btnStopCounters
            // 
            this.btnStopCounters.Enabled = false;
            this.btnStopCounters.Location = new System.Drawing.Point(103, 99);
            this.btnStopCounters.Name = "btnStopCounters";
            this.btnStopCounters.Size = new System.Drawing.Size(87, 23);
            this.btnStopCounters.TabIndex = 5;
            this.btnStopCounters.Text = "Stop Counters";
            this.btnStopCounters.UseVisualStyleBackColor = true;
            this.btnStopCounters.Click += new System.EventHandler(this.btnStopCounters_Click);
            // 
            // cmbDatabaseName
            // 
            this.cmbDatabaseName.FormattingEnabled = true;
            this.cmbDatabaseName.Location = new System.Drawing.Point(125, 24);
            this.cmbDatabaseName.Name = "cmbDatabaseName";
            this.cmbDatabaseName.Size = new System.Drawing.Size(192, 21);
            this.cmbDatabaseName.TabIndex = 1;
            // 
            // label10
            // 
            this.label10.AutoSize = true;
            this.label10.Location = new System.Drawing.Point(8, 27);
            this.label10.Name = "label10";
            this.label10.Size = new System.Drawing.Size(84, 13);
            this.label10.TabIndex = 34;
            this.label10.Text = "Database Name";
            // 
            // cmbCounterSet
            // 
            this.cmbCounterSet.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cmbCounterSet.FormattingEnabled = true;
            this.cmbCounterSet.Items.AddRange(new object[] {
            "Query Performance"});
            this.cmbCounterSet.Location = new System.Drawing.Point(125, 47);
            this.cmbCounterSet.Name = "cmbCounterSet";
            this.cmbCounterSet.Size = new System.Drawing.Size(192, 21);
            this.cmbCounterSet.TabIndex = 2;
            // 
            // label3
            // 
            this.label3.AutoSize = true;
            this.label3.Location = new System.Drawing.Point(8, 50);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(63, 13);
            this.label3.TabIndex = 32;
            this.label3.Text = "Counter Set";
            // 
            // btnStartCounters
            // 
            this.btnStartCounters.Enabled = false;
            this.btnStartCounters.Location = new System.Drawing.Point(8, 99);
            this.btnStartCounters.Name = "btnStartCounters";
            this.btnStartCounters.Size = new System.Drawing.Size(89, 23);
            this.btnStartCounters.TabIndex = 4;
            this.btnStartCounters.Text = "Start Counters";
            this.btnStartCounters.UseVisualStyleBackColor = true;
            this.btnStartCounters.Click += new System.EventHandler(this.btnStartCounters_Click);
            // 
            // txtServerName
            // 
            this.txtServerName.Location = new System.Drawing.Point(125, 2);
            this.txtServerName.Name = "txtServerName";
            this.txtServerName.Size = new System.Drawing.Size(192, 20);
            this.txtServerName.TabIndex = 0;
            this.txtServerName.Leave += new System.EventHandler(this.txtServerName_Leave);
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(8, 6);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(115, 13);
            this.label1.TabIndex = 27;
            this.label1.Text = "SSAS Server\\Instance";
            // 
            // label5
            // 
            this.label5.AutoSize = true;
            this.label5.Location = new System.Drawing.Point(12, 3);
            this.label5.Name = "label5";
            this.label5.Size = new System.Drawing.Size(124, 13);
            this.label5.TabIndex = 30;
            this.label5.Text = "Connect to SSAS Server";
            // 
            // panelRunTest
            // 
            this.panelRunTest.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.panelRunTest.Controls.Add(this.label16);
            this.panelRunTest.Controls.Add(this.label15);
            this.panelRunTest.Controls.Add(this.txtThinkTimeHigh);
            this.panelRunTest.Controls.Add(this.txtAutoStopTimer);
            this.panelRunTest.Controls.Add(this.label14);
            this.panelRunTest.Controls.Add(this.btnClearCache);
            this.panelRunTest.Controls.Add(this.ckShowQueryEnds);
            this.panelRunTest.Controls.Add(this.txtThinkTimeLow);
            this.panelRunTest.Controls.Add(this.label11);
            this.panelRunTest.Controls.Add(this.btnStopTest);
            this.panelRunTest.Controls.Add(this.btnBrowseForQueryFilesPath);
            this.panelRunTest.Controls.Add(this.btnStartTest);
            this.panelRunTest.Controls.Add(this.cmbTestProfile);
            this.panelRunTest.Controls.Add(this.label8);
            this.panelRunTest.Controls.Add(this.txtConcurrentThreads);
            this.panelRunTest.Controls.Add(this.label6);
            this.panelRunTest.Controls.Add(this.label4);
            this.panelRunTest.Controls.Add(this.txtQueryFilesPath);
            this.panelRunTest.Location = new System.Drawing.Point(349, 20);
            this.panelRunTest.Name = "panelRunTest";
            this.panelRunTest.Size = new System.Drawing.Size(441, 128);
            this.panelRunTest.TabIndex = 31;
            // 
            // txtAutoStopTimer
            // 
            this.txtAutoStopTimer.Location = new System.Drawing.Point(404, 59);
            this.txtAutoStopTimer.Name = "txtAutoStopTimer";
            this.txtAutoStopTimer.Size = new System.Drawing.Size(23, 20);
            this.txtAutoStopTimer.TabIndex = 12;
            this.txtAutoStopTimer.Text = "5";
            // 
            // label14
            // 
            this.label14.AutoSize = true;
            this.label14.Location = new System.Drawing.Point(305, 63);
            this.label14.Name = "label14";
            this.label14.Size = new System.Drawing.Size(95, 13);
            this.label14.TabIndex = 43;
            this.label14.Text = "Counter Stop (sec)";
            // 
            // btnClearCache
            // 
            this.btnClearCache.Enabled = false;
            this.btnClearCache.Location = new System.Drawing.Point(176, 93);
            this.btnClearCache.Name = "btnClearCache";
            this.btnClearCache.Size = new System.Drawing.Size(75, 23);
            this.btnClearCache.TabIndex = 14;
            this.btnClearCache.Text = "Clear Cache";
            this.btnClearCache.UseVisualStyleBackColor = true;
            this.btnClearCache.Click += new System.EventHandler(this.btnClearCache_Click);
            // 
            // ckShowQueryEnds
            // 
            this.ckShowQueryEnds.AutoSize = true;
            this.ckShowQueryEnds.Location = new System.Drawing.Point(8, 93);
            this.ckShowQueryEnds.Name = "ckShowQueryEnds";
            this.ckShowQueryEnds.Size = new System.Drawing.Size(146, 17);
            this.ckShowQueryEnds.TabIndex = 13;
            this.ckShowQueryEnds.Text = "Show QueryEnd on Chart";
            this.ckShowQueryEnds.UseVisualStyleBackColor = true;
            // 
            // txtThinkTimeLow
            // 
            this.txtThinkTimeLow.Location = new System.Drawing.Point(172, 59);
            this.txtThinkTimeLow.Name = "txtThinkTimeLow";
            this.txtThinkTimeLow.Size = new System.Drawing.Size(22, 20);
            this.txtThinkTimeLow.TabIndex = 10;
            this.txtThinkTimeLow.Text = "5";
            // 
            // label11
            // 
            this.label11.AutoSize = true;
            this.label11.Location = new System.Drawing.Point(110, 63);
            this.label11.Name = "label11";
            this.label11.Size = new System.Drawing.Size(60, 13);
            this.label11.TabIndex = 39;
            this.label11.Text = "Think Time";
            // 
            // btnStopTest
            // 
            this.btnStopTest.Enabled = false;
            this.btnStopTest.Location = new System.Drawing.Point(359, 93);
            this.btnStopTest.Name = "btnStopTest";
            this.btnStopTest.Size = new System.Drawing.Size(75, 23);
            this.btnStopTest.TabIndex = 16;
            this.btnStopTest.Text = "Stop Test";
            this.btnStopTest.UseVisualStyleBackColor = true;
            this.btnStopTest.Click += new System.EventHandler(this.btnStopTest_Click);
            // 
            // btnBrowseForQueryFilesPath
            // 
            this.btnBrowseForQueryFilesPath.Location = new System.Drawing.Point(338, 4);
            this.btnBrowseForQueryFilesPath.Name = "btnBrowseForQueryFilesPath";
            this.btnBrowseForQueryFilesPath.Size = new System.Drawing.Size(27, 20);
            this.btnBrowseForQueryFilesPath.TabIndex = 7;
            this.btnBrowseForQueryFilesPath.Text = "...";
            this.btnBrowseForQueryFilesPath.UseVisualStyleBackColor = true;
            this.btnBrowseForQueryFilesPath.Click += new System.EventHandler(this.btnBrowseForQueryFilesPath_Click);
            // 
            // btnStartTest
            // 
            this.btnStartTest.Enabled = false;
            this.btnStartTest.Location = new System.Drawing.Point(278, 93);
            this.btnStartTest.Name = "btnStartTest";
            this.btnStartTest.Size = new System.Drawing.Size(75, 23);
            this.btnStartTest.TabIndex = 15;
            this.btnStartTest.Text = "Start Test";
            this.btnStartTest.UseVisualStyleBackColor = true;
            this.btnStartTest.Click += new System.EventHandler(this.btnStartTest_Click);
            // 
            // cmbTestProfile
            // 
            this.cmbTestProfile.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cmbTestProfile.FormattingEnabled = true;
            this.cmbTestProfile.Items.AddRange(new object[] {
            "All queries on each thread (filename order)",
            "All queries on each thread (random order)",
            "Each query on its own thread"});
            this.cmbTestProfile.Location = new System.Drawing.Point(73, 32);
            this.cmbTestProfile.Name = "cmbTestProfile";
            this.cmbTestProfile.Size = new System.Drawing.Size(270, 21);
            this.cmbTestProfile.TabIndex = 8;
            // 
            // label8
            // 
            this.label8.AutoSize = true;
            this.label8.Location = new System.Drawing.Point(5, 35);
            this.label8.Name = "label8";
            this.label8.Size = new System.Drawing.Size(60, 13);
            this.label8.TabIndex = 33;
            this.label8.Text = "Test Profile";
            // 
            // txtConcurrentThreads
            // 
            this.txtConcurrentThreads.Location = new System.Drawing.Point(60, 59);
            this.txtConcurrentThreads.Name = "txtConcurrentThreads";
            this.txtConcurrentThreads.Size = new System.Drawing.Size(34, 20);
            this.txtConcurrentThreads.TabIndex = 9;
            // 
            // label6
            // 
            this.label6.AutoSize = true;
            this.label6.Location = new System.Drawing.Point(5, 63);
            this.label6.Name = "label6";
            this.label6.Size = new System.Drawing.Size(46, 13);
            this.label6.TabIndex = 31;
            this.label6.Text = "Threads";
            // 
            // label4
            // 
            this.label4.AutoSize = true;
            this.label4.Location = new System.Drawing.Point(3, 8);
            this.label4.Name = "label4";
            this.label4.Size = new System.Drawing.Size(83, 13);
            this.label4.TabIndex = 30;
            this.label4.Text = "Path for Queries";
            // 
            // txtQueryFilesPath
            // 
            this.txtQueryFilesPath.Location = new System.Drawing.Point(88, 5);
            this.txtQueryFilesPath.Name = "txtQueryFilesPath";
            this.txtQueryFilesPath.Size = new System.Drawing.Size(245, 20);
            this.txtQueryFilesPath.TabIndex = 6;
            // 
            // label7
            // 
            this.label7.AutoSize = true;
            this.label7.Location = new System.Drawing.Point(351, 4);
            this.label7.Name = "label7";
            this.label7.Size = new System.Drawing.Size(78, 13);
            this.label7.TabIndex = 32;
            this.label7.Text = "Run Load Test";
            // 
            // label9
            // 
            this.label9.AutoSize = true;
            this.label9.Location = new System.Drawing.Point(798, 4);
            this.label9.Name = "label9";
            this.label9.Size = new System.Drawing.Size(86, 13);
            this.label9.TabIndex = 35;
            this.label9.Text = "Generate Report";
            // 
            // panelSaveReport
            // 
            this.panelSaveReport.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
            this.panelSaveReport.Controls.Add(this.label13);
            this.panelSaveReport.Controls.Add(this.txtReportTitle);
            this.panelSaveReport.Controls.Add(this.btnBrowseForReportPath);
            this.panelSaveReport.Controls.Add(this.btnSaveReport);
            this.panelSaveReport.Controls.Add(this.label12);
            this.panelSaveReport.Controls.Add(this.txtReportPath);
            this.panelSaveReport.Enabled = false;
            this.panelSaveReport.Location = new System.Drawing.Point(796, 20);
            this.panelSaveReport.Name = "panelSaveReport";
            this.panelSaveReport.Size = new System.Drawing.Size(310, 128);
            this.panelSaveReport.TabIndex = 34;
            // 
            // label13
            // 
            this.label13.AutoSize = true;
            this.label13.Location = new System.Drawing.Point(5, 32);
            this.label13.Name = "label13";
            this.label13.Size = new System.Drawing.Size(62, 13);
            this.label13.TabIndex = 39;
            this.label13.Text = "Report Title";
            // 
            // txtReportTitle
            // 
            this.txtReportTitle.Location = new System.Drawing.Point(18, 52);
            this.txtReportTitle.Name = "txtReportTitle";
            this.txtReportTitle.Size = new System.Drawing.Size(277, 20);
            this.txtReportTitle.TabIndex = 19;
            // 
            // btnBrowseForReportPath
            // 
            this.btnBrowseForReportPath.Location = new System.Drawing.Point(268, 5);
            this.btnBrowseForReportPath.Name = "btnBrowseForReportPath";
            this.btnBrowseForReportPath.Size = new System.Drawing.Size(27, 20);
            this.btnBrowseForReportPath.TabIndex = 18;
            this.btnBrowseForReportPath.Text = "...";
            this.btnBrowseForReportPath.UseVisualStyleBackColor = true;
            this.btnBrowseForReportPath.Click += new System.EventHandler(this.btnBrowseForReportPath_Click);
            // 
            // btnSaveReport
            // 
            this.btnSaveReport.Location = new System.Drawing.Point(8, 93);
            this.btnSaveReport.Name = "btnSaveReport";
            this.btnSaveReport.Size = new System.Drawing.Size(75, 23);
            this.btnSaveReport.TabIndex = 20;
            this.btnSaveReport.Text = "Save Report";
            this.btnSaveReport.UseVisualStyleBackColor = true;
            this.btnSaveReport.Click += new System.EventHandler(this.btnSaveReport_Click);
            // 
            // label12
            // 
            this.label12.AutoSize = true;
            this.label12.Location = new System.Drawing.Point(3, 8);
            this.label12.Name = "label12";
            this.label12.Size = new System.Drawing.Size(64, 13);
            this.label12.TabIndex = 30;
            this.label12.Text = "Report Path";
            // 
            // txtReportPath
            // 
            this.txtReportPath.Location = new System.Drawing.Point(70, 5);
            this.txtReportPath.Name = "txtReportPath";
            this.txtReportPath.Size = new System.Drawing.Size(192, 20);
            this.txtReportPath.TabIndex = 17;
            // 
            // txtThinkTimeHigh
            // 
            this.txtThinkTimeHigh.Location = new System.Drawing.Point(217, 59);
            this.txtThinkTimeHigh.Name = "txtThinkTimeHigh";
            this.txtThinkTimeHigh.Size = new System.Drawing.Size(23, 20);
            this.txtThinkTimeHigh.TabIndex = 11;
            this.txtThinkTimeHigh.Text = "30";
            // 
            // label15
            // 
            this.label15.AutoSize = true;
            this.label15.Location = new System.Drawing.Point(196, 63);
            this.label15.Name = "label15";
            this.label15.Size = new System.Drawing.Size(16, 13);
            this.label15.TabIndex = 46;
            this.label15.Text = "to";
            // 
            // label16
            // 
            this.label16.AutoSize = true;
            this.label16.Location = new System.Drawing.Point(244, 63);
            this.label16.Name = "label16";
            this.label16.Size = new System.Drawing.Size(47, 13);
            this.label16.TabIndex = 47;
            this.label16.Text = "seconds";
            // 
            // ASLoadTest
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(1122, 818);
            this.Controls.Add(this.CounterTable);
            this.Controls.Add(this.label9);
            this.Controls.Add(this.panelSaveReport);
            this.Controls.Add(this.label7);
            this.Controls.Add(this.panelRunTest);
            this.Controls.Add(this.label5);
            this.Controls.Add(this.panel1);
            this.Controls.Add(this.QuerySummaryTable);
            this.Controls.Add(this.MemoryChartHost);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
            this.MaximizeBox = false;
            this.Name = "ASLoadTest";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "AS Performance Workbench";
            this.Load += new System.EventHandler(this.QueryLoadForm_Load);
            ((System.ComponentModel.ISupportInitialize)(this.QuerySummaryTable)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.CounterTable)).EndInit();
            this.panel1.ResumeLayout(false);
            this.panel1.PerformLayout();
            this.panelRunTest.ResumeLayout(false);
            this.panelRunTest.PerformLayout();
            this.panelSaveReport.ResumeLayout(false);
            this.panelSaveReport.PerformLayout();
            this.ResumeLayout(false);
            this.PerformLayout();

		}
		#endregion

        //*************************************************************************************************
        //
        //          Function: QueryLoadForm_Load
        //       Description: Form initialization
        //
        //*************************************************************************************************
        private void QueryLoadForm_Load(object sender, System.EventArgs e)
		{
            //------------------   Initialize the Memory Chart ----------------------
            _MemoryChart = new Chart();
            _MemoryChart.Width = 200;
            _MemoryChart.Height = 120;
            _MemoryChart.AnimationEnabled = false;
            _MemoryChart.ScrollingEnabled = false;
            _MemoryChart.Watermark = false;
            _MemoryChart.Background = new SolidColorBrush(Colors.White);
            _MemoryChart.LightingEnabled = false;
            _MemoryChart.ShadowEnabled = false;

            Visifire.Charts.Axis axisY = new Visifire.Charts.Axis();
            ChartGrid yGrid = new ChartGrid();
            yGrid.Enabled = false;
            axisY.Grids.Add(yGrid);
            _MemoryChart.AxesY.Add(axisY);

            //-----------  setup the secondary axis (for Processor %)  ---------------
            axisY = new Visifire.Charts.Axis();
            axisY.AxisType = AxisTypes.Secondary;
            yGrid = new ChartGrid();
            yGrid.Enabled = false;
            axisY.Grids.Add(yGrid);
            _MemoryChart.AxesY.Add(axisY);

            //-------------  No X-Axis labels  -------------------
            Visifire.Charts.Axis axisX = new Visifire.Charts.Axis();
            axisX.Enabled = false;
            _MemoryChart.AxesX.Add(axisX);

            MemoryChartHost.Child = _MemoryChart;

        }

        //*************************************************************************************************
        //
        //          Function: SetupChart
        //       Description: called whenever the chart's design has changed (e.g. user removes/adds a line)
        //
        //*************************************************************************************************
        private void SetupChart()
        {
            
            _MemoryChart.Series.Clear();

            ArrayList ar = new ArrayList();

            try
            {

                foreach (CounterData item in _CounterDataList)
                {
                    if (item.summaryRow.Enabled)
                    {
                        //---------- Add a line to the graph  -----------
                        SetupChartLine(item.summaryRow.Name, ref _MemoryChart, item.series, item.summaryRow.GetCounterColor(), item.axis, item.summaryRow.GetLineStyle(), item.summaryRow.GetLineThickness());
                    }
                    //----------  Add each summary row for display in the grid  ---------------
                    ar.Add(item.summaryRow);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error setting up chart: " + ex.Message);
            }

            CounterTable.DataSource = ar;
            FormatCounterTable();
        }

        //*************************************************************************************************
        //
        //          Function: SetupChartLine
        //       Description: Adds a single line to the chart 
        //
        //*************************************************************************************************
        private void SetupChartLine(string LegendText, ref Chart chart, 
                                    DataSeries ds, System.Windows.Media.Color color,
                                    AxisTypes axisType, LineStyles lineStyle, double lineThickness)
        {
            try
            {
                //---------------  Add the high memory limit to the chart  ----------------
                int newSeriesNum = chart.Series.Count;

                //--------------  Add the counter to the chart  ----------------------
                chart.Series.Add(ds);
                chart.Series[newSeriesNum].Color = new SolidColorBrush(color);
                chart.Series[newSeriesNum].ShadowEnabled = false;
                chart.Series[newSeriesNum].MarkerEnabled = false;
                chart.Series[newSeriesNum].AxisYType = axisType;
                chart.Series[newSeriesNum].LegendText = LegendText;
                chart.Series[newSeriesNum].LineThickness = lineThickness;
                chart.Series[newSeriesNum].LineStyle = lineStyle;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error initializing chart: " + ex.Message);
            }
        }

        //*************************************************************************************************
        //
        //          Function: UpdateChart
        //       Description: Updates the perfmon lines with the current counter values
        //
        //*************************************************************************************************
        public void UpdateChart()
        {
            ArrayList ar = new ArrayList();

            foreach (CounterData item in _CounterDataList)
            {
                item.AddDataPoint();
                ar.Add(item.summaryRow);
            }

            CounterTable.DataSource = ar;  // cause the grid to refresh itself
            FormatCounterTable();
        }


        //*************************************************************************************************
        //
        //          Function: Start_QueryWorkers
        //       Description: Start queries running on all thrads
        //
        //*************************************************************************************************
        private void Start_QueryWorkers()
        {

            _swOverallRuntime.Reset();
            _swOverallRuntime.Start();

            foreach (QueryThread th in _QueryWorkers)
            {
                SSASQuery q = new SSASQuery();
                q.ThreadName = th.threadName;
                q.QueryFileName = th.queryList.GetByIndex(th.nextQueryIndex++) as string;
                q.QueryText = GetQueryFileContent(q.QueryFileName);

                if (q.QueryFileName.ToUpper().EndsWith("XMLA"))
                    q.QueryType = SSASQuery.QueryTypes.XMLAQuery;
                else
                    q.QueryType = SSASQuery.QueryTypes.MDXQuery;

                q.queryThread = th;
                th.WorkerThread.RunWorkerAsync(q);
            }
        }

        //*************************************************************************************************
        //
        //          Function: IsTestRunning
        //       Description: Checks if all threads are completed
        //
        //*************************************************************************************************
        private bool IsTestRunning()
        {
            bool isThreadRunning = false;

            foreach (QueryThread th in _QueryWorkers)
            {
                if (!th.isFinished)
                {
                    isThreadRunning = true;
                    break;
                }
            }

            return isThreadRunning;
        }


        //*************************************************************************************************
        //
        //          Function: GetQueryFileContent
        //       Description: Gets a query file from disk as a string to pass to the server
        //
        //*************************************************************************************************
        private string GetQueryFileContent(string FileName)
        {
            int maxFileSize = 64535;
            char[] buf = new char[maxFileSize];
            string fullPath = txtQueryFilesPath.Text.Trim() + "\\" + FileName;
            string filereadbuf = "";

            try
            {
                StreamReader sr = new StreamReader(new FileStream(fullPath, FileMode.Open, FileAccess.Read));
                int retval = sr.ReadBlock(buf, 0, maxFileSize); // no. of bytes read 
                filereadbuf = new string(buf); // store it in our field 
                sr.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            return filereadbuf;
        }

        //*************************************************************************************************
        //
        //          Function: QueryWorkerThread_DoWork
        //       Description: Respond to thread event to start doing the thread's work
        //
        //*************************************************************************************************
        private void QueryWorkerThread_DoWork(object sender, DoWorkEventArgs e)
        {
            lock (_lockActiveQueries)
            {
                _ActiveQueries++;
            }

            SSASQuery q = e.Argument as SSASQuery;

            //-----------  Record the starting time  ----------------
            Stopwatch sw = new Stopwatch();
            sw.Start();
            q.startDt = DateTime.Now;

            //--------------  Run the query now  ------------------------
            try
            {
                if (q.QueryType != SSASQuery.QueryTypes.XMLAQuery)
                {
                    OleDbCommand cmd = new OleDbCommand(q.QueryText, q.queryThread.conn); // note: using the connection of the thread we're assigned to
                    OleDbDataReader rdr = cmd.ExecuteReader();
                    q.OutcomeText = "FieldCount=" + rdr.FieldCount.ToString();
                    rdr.Close();
                }
            }
            catch (Exception ex)
            {
                q.OutcomeText = "Exception: " + ex.Message;
                q.queryException = true;
            }

            //-----------  Record the ending time  ---------------------
            sw.Stop();
            q.durationMilliseconds = sw.ElapsedMilliseconds;
            q.endDt = DateTime.Now;

            lock (_lockActiveQueries)
            {
                _ActiveQueries--;
            }

            //--------------  Pause this thread for think time if required  ---------------
            if (_ThinkTimeLowSec > 0 && _ThinkTimeHighSec > 0)
            {
                Random r = new Random();
                int randomThink = r.Next(_ThinkTimeLowSec, _ThinkTimeHighSec) * 1000;
                Thread.Sleep(randomThink);
            }

            //------  The thread returns the query it's been working on to the Completed handler  -------
            e.Result = q;
        }

        //*************************************************************************************************
        //
        //          Function: QueryWorkerThread_RunWorkerCompleted
        //       Description: Event hander called whenever a thread has finished running a query
        //
        //*************************************************************************************************
        private void QueryWorkerThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //----------  Get a reference to the query this thread has been working on ------------
            SSASQuery q = e.Result as SSASQuery;

            if (q.QueryType == SSASQuery.QueryTypes.ClearFileSystemCache)
            {
                this.Cursor = Cursors.Default;
                MessageBox.Show("ASSP.ClearAllCaches() completed.");
            }
            else
            {
                //-------------  Add a vertical trend line to indicate this query has finished  -----------------
                if (q.queryException || ckShowQueryEnds.Checked)
                {
                    TrendLine tl = new TrendLine();

                    if (q.queryException)
                        tl.LineColor = new SolidColorBrush(Colors.Red);  // red=an error occurred
                    else
                        tl.LineColor = new SolidColorBrush(Colors.DarkGreen); // green=normal finish (as far as can tell)

                    tl.LineThickness = 1;
                    tl.ToolTipText = q.QueryFileName + " finished on thread " + q.queryThread.threadName;
                    tl.Enabled = true;
                    tl.LineStyle = LineStyles.Solid;
                    tl.Orientation = System.Windows.Controls.Orientation.Vertical;
                    CounterData item = _CounterDataList[0] as CounterData;
                    tl.Value = item.series.DataPoints.Count - 1;

                    lock (_MemoryChart)
                    {
                        _MemoryChart.TrendLines.Add(tl);
                    }
                }
            }

            //-------------  Add a record of this query finishing to the end of the array list and reset the grid -------
            lock (QuerySummaryTable)
            {
                ArrayList ar = new ArrayList();
                ar.AddRange(QuerySummaryTable.DataSource as ArrayList);
                ar.Add(q);

                QuerySummaryTable.DataSource = ar;
            }


            //----------- If we're running MDX or XMLA queries, run the next one. --------------
            //----------- If not, we must have just cleared caches, so  call the routine to launch parallel load testing---------------
            if ( (q.QueryType == SSASQuery.QueryTypes.MDXQuery || q.QueryType == SSASQuery.QueryTypes.XMLAQuery))
            {
                if (!_TestCanceled && q.queryThread.nextQueryIndex < q.queryThread.queryList.Count)
                {
                    SSASQuery newQuery = new SSASQuery();
                    newQuery.QueryFileName = q.queryThread.queryList.GetByIndex(q.queryThread.nextQueryIndex++) as string;
                    newQuery.QueryText = GetQueryFileContent(newQuery.QueryFileName);

                    if (newQuery.QueryFileName.ToUpper().EndsWith("XMLA"))
                        newQuery.QueryType = SSASQuery.QueryTypes.XMLAQuery;
                    else
                        newQuery.QueryType = SSASQuery.QueryTypes.MDXQuery;

                    newQuery.queryThread = q.queryThread;
                    newQuery.ThreadName = q.queryThread.threadName;

                    q.queryThread.WorkerThread.RunWorkerAsync(newQuery);
                }
                else
                    q.queryThread.isFinished = true;  // exceeded count, so we are done
            }
        }

        //*************************************************************************************************
        //
        //          Function: ChangeCounterState
        //       Description: User has clicked on a checkbox to disable/enable acounter
        //
        //*************************************************************************************************
        private void ChangeCounterState(string counterName, Boolean bEnable)
        {
            foreach (CounterData item in _CounterDataList)
            {
                if (counterName.CompareTo(item.summaryRow.Name) == 0)
                {
                    item.summaryRow.Enabled = bEnable;
                    SetupChart();
                }
            }
        }

        //*************************************************************************************************
        //
        //          Function: CounterTable_CellContentClick
        //       Description: User has clicked in a cell. Check if they clicked a checkbox, and if so
        //                    respond by changing the counter state they indicated.
        //
        //*************************************************************************************************
        private void CounterTable_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {
            //--------- If the enable/disable button clicked, process it  ------
            if (e.ColumnIndex == 0)
            {
                CounterTable.EndEdit();
                Boolean bEnabled = (Boolean)CounterTable.Rows[e.RowIndex].Cells["Enabled"].Value;
                string CounterName = (string)CounterTable.Rows[e.RowIndex].Cells["Name"].Value;
                ChangeCounterState(CounterName, bEnabled);
            }
        }

        //*************************************************************************************************
        //
        //          Function: FormatCounterTable
        //       Description: Apply formatting to the counter table
        //
        //*************************************************************************************************
        private void FormatCounterTable()
        {
            try
            {
                CounterTable.Columns["Current"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.TopRight;
                CounterTable.Columns["Avg"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.TopRight;
                CounterTable.Columns["Max"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.TopRight;
                CounterTable.Columns["Min"].DefaultCellStyle.Alignment = DataGridViewContentAlignment.TopRight;
                CounterTable.Columns["Enabled"].Width = 50;
                CounterTable.BackgroundColor = System.Drawing.Color.Honeydew;
                CounterTable.Columns["Name"].Width = 180;
            }
            catch (Exception)
            { 
                //oops, I guess this table doesn't exist yet
            }
        }

        //*************************************************************************************************
        //
        //          Function: btnConnect_Click
        //       Description: User presses button to begin monitoring
        //
        //*************************************************************************************************
        private void btnStartCounters_Click(object sender, EventArgs e)
        {
            //-----------  Validate  -------------
            if (txtServerName.Text.Trim().Length == 0)
                MessageBox.Show("Please enter a server name to connect to");
            else
            {
                Create_CounterDataList();   // create data structures to track counter data
                SetupChart();               // initialize the chart 
                _Connected = true;          // flag that we're connected 
                Clock.Start();              // start the timer

                btnStartCounters.Enabled = false;
                btnStopCounters.Enabled = true;
                btnStartTest.Enabled = true;
            }
        }

        //*************************************************************************************************
        //
        //          Function: btnStopCounters_Click
        //       Description: User presses button to stop counters and stop accumulating data
        //
        //*************************************************************************************************
        private void btnStopCounters_Click(object sender, EventArgs e)
        {
            Clock.Stop();
            btnStopCounters.Enabled = false;
            btnStartCounters.Enabled = true;
        }


        //*************************************************************************************************
        //
        //          Function: btnSaveReport_Click
        //       Description: Creates report output as an HTML file in the specified report folder.
        //                    A time-coded sub-folder is created beneath the folder specified in the 
        //                    form field.
        //
        //*************************************************************************************************
        private void btnSaveReport_Click(object sender, EventArgs e)
        {
            //------------- Build the path to a sub-folder of the specified report folder  ---------
            string strOutputFolder = txtReportPath.Text.Trim().ToString() + @"\" +
                                    DateTime.Now.Year.ToString() +
                                    DateTime.Now.Month.ToString("00") +
                                    DateTime.Now.Day.ToString("00") +
                                    DateTime.Now.Hour.ToString("00") +
                                    DateTime.Now.Minute.ToString("00") +
                                    DateTime.Now.Second.ToString("00");

            //----------  Create the subfolder, then write the report to it  -------------
            try
			{
                // create the folder
				System.IO.Directory.CreateDirectory(strOutputFolder);

                // write the chart as a .jpg
                _MemoryChart.Export(strOutputFolder + @"\PerformanceChart", ExportType.Jpg);

                // write the report 
                using (StreamWriter outfile = new StreamWriter(strOutputFolder + @"\default.htm"))
                {
                    //-----------  Beginning of HTML  ---------------------
                    outfile.Write(
                        @"<html><head>" + 
                        @"<style type=""text/css"">" +
                        @"table, td, th {border:0px; padding:5px; } " +
                        @"td, th {font-family:""Verdana""; font-size:12px;} " +
                        @"th {background-color:darkblue; color:white; align=""Right""}" + 
                        @"</style>" +
                        @"</head><body><font face=""Arial"" size=""4"">");

                    //-----------  Heading  ------------------
                    outfile.Write(@"<table width=""1098"">" +
                                  @"<tr><td align=""center"" style=""background-color:darkblue; color:white;""><h2>AS Performance Workbench</h2>" +
                                  @"<h3>" + txtReportTitle.Text + "</h3>" + 
                                  DateTime.Now.ToString("MM/dd/yy H:mm:ss") + "</td></tr>");

                    //-------------  Test Parameters ---------------------
                    outfile.Write(@"<tr><td><table>" +
                                  @"<tr><td colspan=""2""><h3>Test Parameters</h3></td></tr>" +
                                  @"<tr><td>Server Name</td><td>" + txtServerName.Text + "</td></tr>" +
                                  @"<tr><td>Database Name</td><td>" + cmbDatabaseName.Text + "</td></tr>" +
                                  @"<tr><td>Counter Set</td><td>" + cmbCounterSet.Text + "</td></tr>" +
                                  @"<tr><td>Counter Set</td><td>" + cmbSQLVersion.Text + "</td></tr>" +
                                  @"<tr><td>Query Files Path</td><td>" + txtQueryFilesPath.Text + "</td></tr>" +
                                  @"<tr><td>Test Profile</td><td>" + cmbTestProfile.Text + "</td></tr>" +
                                  @"<tr><td>Concurrent Threads</td><td>" + txtConcurrentThreads.Text + "</td></tr>" +
                                  @"<tr><td>Think Time</td><td>" + txtThinkTimeLow.Text + " to " + txtThinkTimeHigh.Text + "</td></tr>");

                    outfile.Write(@"<tr><td>Overall test duration</td><td>" + ((float)_swOverallRuntime.ElapsedMilliseconds / 1000.0).ToString("N1") + " seconds</td></tr>");

                    outfile.Write("</table></td></tr>");

                    //---------------  Peformance Chart  ----------------
                    outfile.Write(@"<tr><td><h3>Performance Chart</h3></td></tr>");
                    outfile.Write(@"<tr><td><img src=""PerformanceChart.jpg""></td></tr>");

                    //---------- Add report tables  --------------
                    PerfCounterReport(outfile);
                    QuerySummaryReport(outfile);
                    AllQueriesRunReport(outfile);

                    outfile.Write(@"</table></body></html>");
                }

                System.Diagnostics.Process.Start(strOutputFolder + @"\default.htm");
			}
			catch(Exception ex)
			{
                MessageBox.Show(ex.Message);
            }
        }

        //*************************************************************************************************
        //
        //          Function: PerfCounterReport
        //       Description: Output the HTML for a report detailing the performance counter information
        //
        //*************************************************************************************************
        private void PerfCounterReport(StreamWriter outfile)
        {
            outfile.Write(@"<tr><td><table cellpadding=""5""><tr><td colspan=""5""><h3>Performance Counter Summary</h3></td></tr>");
            outfile.Write(@"<tr><th align=""left"">Counter Name</th>" + 
                          @"<th align=""right"">Last Value</th>" + 
                          @"<th align=""right"">Avg Value</th>" + 
                          @"<th align=""right"">Min Value</th>" + 
                          @"<th align=""right"">Max Value</th></tr>");

            ArrayList ar = CounterTable.DataSource as ArrayList;

            foreach (CounterSummaryRow row in ar)
            {
                outfile.Write( @"<tr><td align=""left"">" + row.Name + 
                               @"</td><td align=""right"">" + row.Current + 
                               @"</td><td align=""right"">" + row.Avg + 
                               @"</td><td align=""right"">" + row.Min +
                               @"</td><td align=""right"">" + row.Max + @"</td></tr>");                   
            }

            outfile.Write(@"</table></td></tr>");
        }

        //*************************************************************************************************
        //
        //          Function: QuerySummaryReport
        //       Description: Output the HTML all the queries that were run, by filename
        //
        //*************************************************************************************************
        private void QuerySummaryReport(StreamWriter outfile)
        {
            outfile.Write(@"<tr><td><table width=""1098"" cellpadding=""5""><tr><td colspan=""5""><h3>Query Summary Statistics</h3></td></tr>");
            outfile.Write(@"<tr>" +
                          @"<th align=""left"">Query Filename</th>" +
                          @"<th align=""right"">Execution Count</th>" +
                          @"<th align=""right"">Avg Duration</th>" +
                          @"<th align=""right"">Max Duration</th>" +
                          @"<th align=""right"">Min Duration</th>" +
                          @"<th align=""right"">Error Count</th></tr>");


            //-----------  Build a list of distinct queries  ----------------
            ArrayList ar = QuerySummaryTable.DataSource as ArrayList;
            SortedList sa = new SortedList();

            foreach (SSASQuery row in ar)
            {
                if (!sa.ContainsKey(row.QueryFileName))
                {
                    QueryReportRow summaryRow = 
                        new QueryReportRow(row.QueryFileName, row.QueryText, row.durationMilliseconds, row.queryException);
                    sa.Add(row.QueryFileName, summaryRow);
                }
                else
                {
                    QueryReportRow summaryRow = sa[row.QueryFileName] as QueryReportRow;
                    summaryRow.addSample(row.durationMilliseconds, row.queryException);
                }
            }

            //------------  Output the list of queries  -----------------
            for (int i = 0; i < sa.Count; i++)
            {
                QueryReportRow row = sa.GetByIndex(i) as QueryReportRow;

                outfile.Write(@"<tr><td align=""left"">" + sa.GetKey(i) +
                               @"</td><td align=""right"">" + row.NumExecutions.ToString("N0") +
                               @"</td><td align=""right"">" + row.durationAvgMs.ToString("N0") +
                               @"</td><td align=""right"">" + row.durationMaxMs.ToString("N0") +
                               @"</td><td align=""right"">" + row.durationMinMs.ToString("N0") +
                               @"</td><td align=""right"">" + row.NumExceptions.ToString("N0") +
                               @"</td></tr>");
            }

            outfile.Write(@"</table></td></tr>");
        }

        //*************************************************************************************************
        //
        //          Function: AllQueriesRunReport
        //       Description: Output the HTML for a report listing each thread with its query performance
        //
        //*************************************************************************************************
        private void AllQueriesRunReport(StreamWriter outfile)
        {
            outfile.Write(@"<tr><td><table width=""1098"" cellpadding=""5""><tr><td colspan=""6""><h3>All Query Executions</h3></td></tr>");
            outfile.Write(@"<tr>" +
                          @"<th align=""left"">Thread Name</th>" + 
                          @"<th align=""left"">Query Filename</th>" +
                          @"<th align=""left"">Result</th>" +
                          @"<th align=""right"">Start Time</th>" + 
                          @"<th align=""right"">End Time</th>" + 
                          @"<th align=""right"">Duration (ms)</th></tr>");

            ArrayList ar = QuerySummaryTable.DataSource as ArrayList;

            foreach (SSASQuery row in ar)
            {
                outfile.Write(@"<tr><td align=""left"">" + row.ThreadName +
                               @"</td><td align=""left"">" + row.QueryFileName +
                               @"</td><td align=""left"">" + row.OutcomeText +
                               @"</td><td align=""right"">" + row.startDt.ToString("H:mm:ss") +
                               @"</td><td align=""right"">" + row.endDt.ToString("H:mm:ss") +
                               @"</td><td align=""right"">" + row.durationMilliseconds.ToString("N0") +
                               @"</td></tr>");
            }

            outfile.Write(@"</table></td></tr>");
        }


        //*************************************************************************************************
        //
        //          Function: btnBrowseForQueryFilesPath_Click
        //       Description: Allow the user to choose the folder where queries to run are stored
        //
        //*************************************************************************************************
        private void btnBrowseForQueryFilesPath_Click(object sender, EventArgs e)
        {
            _fbd.Description = "Select the folder containing the .MDX or .XMLA commands to use for load testing";
            DialogResult result = _fbd.ShowDialog();

            if (result==DialogResult.OK)
                txtQueryFilesPath.Text = _fbd.SelectedPath;
        }

        //*************************************************************************************************
        //
        //          Function: btnBrowseForReportPath_Click
        //       Description: Use common dialog to browse for the report path
        //
        //*************************************************************************************************
        private void btnBrowseForReportPath_Click(object sender, EventArgs e)
        {
            _fbd.Description = "Select the folder where test output reports should be saved";
            DialogResult result = _fbd.ShowDialog();

            if (result == DialogResult.OK)
                txtReportPath.Text = _fbd.SelectedPath;
        }


        //*************************************************************************************************
        //
        //          Function: btnStartTest_Click
        //       Description: User presses the button to begin the load test
        //
        //*************************************************************************************************
        private void btnStartTest_Click(object sender, EventArgs e)
        {
            bool bContinue = true;
            string strFolderName="";
            int numThreads=1;

            //-------------  Check if counters are running, error if they are not --------
            if (btnStartCounters.Enabled)
            {
                MessageBox.Show("Start counters before beginning a load test");
                bContinue = false;
                return;
            }

            //------------ Input validation ----------------
            if (!_Connected)
            {
                MessageBox.Show("Connect to an SSAS instance before running tests");
                bContinue = false;
            }
            else if (cmbDatabaseName.Text.Trim().Length == 0)
            {
                MessageBox.Show("Please select a database name from the database list before starting your test");
                bContinue = false;
            }
            else
            {
                bool bIsNumber = Int32.TryParse(txtConcurrentThreads.Text.Trim(), out numThreads);
                if (!bIsNumber)
                {
                    MessageBox.Show("The value in [Concurrent Threads] cannot be converted to an integer");
                    bContinue = false;
                }
                else if (numThreads < 1)
                {
                    MessageBox.Show("The value in [Concurrent Threads] must be greater than zero");
                    bContinue = false;
                }
            }

            if (bContinue)
            {
                if (txtThinkTimeLow.Text.Trim().Length == 0)
                    txtThinkTimeLow.Text = "0";

                if (txtThinkTimeHigh.Text.Trim().Length == 0)
                    txtThinkTimeHigh.Text = "0";

                bool bIsNumber = Int32.TryParse(txtThinkTimeLow.Text.Trim(), out _ThinkTimeLowSec);
                if (!bIsNumber)
                {
                    MessageBox.Show("The value in [Think Time (from)] cannot be converted to an integer");
                    bContinue = false;
                }
                else if (_ThinkTimeLowSec < 1)
                {
                    MessageBox.Show("The value in [Think Time (from)] must be greater than or equal to zero");
                    bContinue = false;
                }

                bIsNumber = Int32.TryParse(txtThinkTimeHigh.Text.Trim(), out _ThinkTimeHighSec);
                if (!bIsNumber)
                {
                    MessageBox.Show("The value in [Think Time (high)] cannot be converted to an integer");
                    bContinue = false;
                }
                else if (_ThinkTimeHighSec < 1)
                {
                    MessageBox.Show("The value in [Think Time (high)] must be greater than or equal to zero");
                    bContinue = false;
                }
            }

            //----------- if passed validation, go get the files ------------------
            if (bContinue)
            {
                _QueryList.Clear();

                try
                {
                    //-------------  Get the list of files in the specified folder  ---------------
                    strFolderName = txtQueryFilesPath.Text.Trim();
                    System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(strFolderName);

                    foreach (System.IO.FileInfo f in dir.GetFiles("*.*"))
                        _QueryList.Add(f.Name);

                    if (_QueryList.Count == 0)
                    {
                        MessageBox.Show("The folder specified contains no files!");
                        bContinue = false;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                    bContinue = false;
                }
            }

            //----------  If one query per thread, make sure user has matched query count and thread count  --------
            if (bContinue && cmbTestProfile.Text.CompareTo("Each query on its own thread") == 0)
            {
                if (_QueryList.Count != numThreads)
                {
                    DialogResult result =
                        MessageBox.Show("Because you specified each query to run on its own thread, " +
                                     "the number of threads will be changed to " + _QueryList.Count.ToString() +
                                     " to match the number of files found in folder [" + strFolderName + "]. Is this OK?",
                                     "Thread/Query Mismatch", MessageBoxButtons.YesNoCancel);

                    if (result == DialogResult.Yes)
                    {
                        numThreads = _QueryList.Count;
                        txtConcurrentThreads.Text = numThreads.ToString();
                    }
                    else
                    {
                        bContinue = false;
                    }
                }
            }

            //-------------  All validations completed, start the test  ---------------
            if (bContinue)
            {
                _TestCanceled = false;
                btnStopTest.Enabled=true;
                btnStartTest.Enabled = false;

                //---------- Build the correct server connect string  --------------
                string strServer = _serverName;
                if (_instanceName != null && _instanceName.Length > 0)
                    strServer += "\\" + _instanceName;

                string strConnString = "Provider=MSOLAP;Data Source=" + strServer + ";Initial Catalog=" + cmbDatabaseName.Text.Trim() + ";";

                //--------------  Clear the old query results  -------------
                ArrayList temp1 = new ArrayList();
                QuerySummaryTable.DataSource = temp1;


                //-------------  Reset the worker threads  ---------------------------
                _QueryWorkers.Clear();
                _QueryWorkers = new ArrayList();
                
                //-------------   Create the worker threads & connections  ------------- 
                for (int i = 0; i < numThreads; i++)
                {
                    // create the thread workload
                    QueryThread thread = new QueryThread(strConnString);
                    thread.threadName = "Thread " + i.ToString();

                    // assign it a background worker thread, and assign event handlers
                    thread.WorkerThread = new BackgroundWorker();
                    thread.WorkerThread.DoWork += new System.ComponentModel.DoWorkEventHandler(this.QueryWorkerThread_DoWork);
                    thread.WorkerThread.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.QueryWorkerThread_RunWorkerCompleted);


                    // assign queries to the thread
                    if (cmbTestProfile.Text.CompareTo("Each query on its own thread") == 0)
                    {
                        // one query per thread, so assign the "i"th query to this thread.  
                        if (i < _QueryList.Count)
                        {
                            thread.queryList.Add(_QueryList[i], _QueryList[i]);                            
                        }
                    }
                    else
                    {  // all queries for all threads
                        Random random = new Random();

                        foreach (string queryText in _QueryList)
                        {
                            if (cmbTestProfile.Text.CompareTo("All queries on each thread (random order)") == 0)
                            {
                                thread.queryList.Add(random.Next().ToString(), queryText);
                            }
                            else // All queries on each thread (filename order)
                            {
                                thread.queryList.Add(queryText, queryText);
                            }
                        } // foreach
                    } // else

                    // Add the items in our tempQueryList to the thread's ArrayList

                    // add to the workers collection 
                    _QueryWorkers.Add(thread);
                }

                // all setup done, start the worker threads
                Start_QueryWorkers();

            } // all checks comlete
        } // btnStart_Clicked

        //*************************************************************************************************
        //
        //          Function: btnStopTest_Click
        //       Description: User presses the button to cancel the load test
        //
        //*************************************************************************************************
        private void btnStopTest_Click(object sender, EventArgs e)
        {
            _TestCanceled = true;
            btnStopTest.Enabled=false;
        }

        //*************************************************************************************************
        //
        //          Function: txtServerName_Leave
        //       Description: When user leaves the server field, parse server & instance
        //
        //*************************************************************************************************
        private void txtServerName_Leave(object sender, EventArgs e)
        {
            string nameAsEntered = txtServerName.Text.Trim();
            cmbDatabaseName.Items.Clear();

            int pos = nameAsEntered.IndexOf('\\');
            if (pos == -1)
            {
                _serverName = nameAsEntered;
                _instanceName = null;
            }
            else
            {
                _serverName = nameAsEntered.Substring(0, pos);
                _instanceName = nameAsEntered.Substring(pos+1);
            }


            if (_serverName != null && _serverName.Length > 0)
            {
                string strServerName = _serverName;
                this.Cursor = Cursors.WaitCursor;

                if (_instanceName != null && _instanceName.Length > 0)
                    strServerName = _serverName + "\\" + _instanceName;

                if (strServerName.Length > 0)
                {
                    try
                    {
                        Server svr = new Server();
                        svr.Connect(strServerName);

                        foreach (Database db in svr.Databases)
                        {
                            cmbDatabaseName.Items.Add(db.Name);
                        }

                        svr.Disconnect();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }

                this.Cursor = Cursors.Default;

                if (cmbDatabaseName.Items.Count > 0)
                {
                    cmbDatabaseName.SelectedIndex = 0;
                    btnStartCounters.Enabled = true;
                    btnClearCache.Enabled = true;
                }
            }
        }

        private void btnClearCache_Click(object sender, EventArgs e)
        {
            string strServer = _serverName;
            if (_instanceName != null && _instanceName.Length > 0)
                strServer += "\\" + _instanceName;

            string strConnString = "Provider=MSOLAP;Data Source=" + strServer + ";Initial Catalog=" + cmbDatabaseName.Text.Trim() + ";";

            //------------  Create a worker thread for clearcache ----------------
            QueryThread thread = new QueryThread(strConnString);
            thread.threadName = "Thread ClearCache";

            // assign it a background worker thread, and assign event handlers
            thread.WorkerThread = new BackgroundWorker();
            thread.WorkerThread.DoWork += new System.ComponentModel.DoWorkEventHandler(this.QueryWorkerThread_DoWork);
            thread.WorkerThread.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.QueryWorkerThread_RunWorkerCompleted);

            SSASQuery q = new SSASQuery();
            q.QueryType = SSASQuery.QueryTypes.ClearFileSystemCache;
            q.QueryFileName = "(ClearCache)";
            q.QueryText = "call ASSP.ClearAllCaches()";
            q.queryThread = thread;
            q.ThreadName = thread.threadName;

            _QueryWorkers.Add(thread);

            // begin the clearcache command
            ASLoadTest.ActiveForm.Cursor = Cursors.WaitCursor;
            thread.WorkerThread.RunWorkerAsync(q);
        }

        
    } // class



    //*************************************************************************************************
    //
    //          Class: SSASQuery
    //    Description: This class encapsulates data about a query run.  It's used as a source for the 
    //                 query result grid and for reporting
    //
    //*************************************************************************************************
    class SSASQuery
    {
        public enum QueryTypes { ClearSSASCache, ClearFileSystemCache, ClearAllCaches, MDXQuery, XMLAQuery };

        public QueryTypes QueryType { get; set; }
        public string ThreadName { get; set; }
        public string QueryFileName { get; set; }
        public string QueryText { get; set; }
        public string OutcomeText { get; set; }
        public DateTime startDt { get; set; }
        public DateTime endDt { get; set; }
        public long durationMilliseconds { get; set; }
        public QueryThread queryThread { get; set; }  // this is the thread this query object is running on
        public bool queryException { get; set; }
    }

    //*************************************************************************************************
    //
    //          Class: CounterSummaryRow
    //    Description: This class is used to populate the counter grid
    //
    //*************************************************************************************************
    class CounterSummaryRow
    {
        public CounterSummaryRow(string CounterName, System.Windows.Media.Color color, LineStyles lineStyle, double lineThickness, string format, bool enabled)
        {
            this.Enabled=enabled;
            this.Name = CounterName;
            this._CounterColor = color;
            this._LineStyle = lineStyle;
            this._LineThickness = lineThickness;
            this._format = format;
            this._min = 99999999;
        }
        public Boolean Enabled { get; set; }

        protected System.Windows.Media.Color _CounterColor;
        public System.Windows.Media.Color GetCounterColor() { return _CounterColor;  }

        protected LineStyles _LineStyle;
        public LineStyles GetLineStyle() { return _LineStyle; }

        protected double _LineThickness;
        public double GetLineThickness() { return _LineThickness; }

        public string Name { get; set; }

        //------- Current value  ----------------------
        protected float _current;
        public void SetCurrent(float f) { _current = f; }
        public string Current {
            get { return this._current.ToString(this._format); }
        }

        //------ AVG value  ---------------
        protected float _avg;
        public void SetAvg(float f) { _avg = f; }
        public string Avg
        {
            get { return this._avg.ToString(this._format); }
        }

        //------ MAX value  ---------------
        protected float _max;
        public void SetMax(float f) { _max = f; }
        public float GetMax() { return _max; }
        public string Max
        {
            get { return this._max.ToString(this._format); }
        }

        //------ MIN value  ---------------
        protected float _min;
        public void SetMin(float f) { _min = f; }
        public float GetMin() { return _min; }
        public string Min
        {
            get { return this._min.ToString(this._format); }
        }

        protected string _format;
    }

    //*************************************************************************************************
    //
    //          Class: CounterData
    //    Description: This class is used to store data about a counter, how to display it in the chart
    //                 as well as storing the data points making up the counter's time series data.
    //                 this class is used to populate a CounterSummaryRow when it's time to update the grid
    //
    //*************************************************************************************************
    class CounterData
    {
        public CounterData(
            string Machine, string CounterCategory, 
            string CounterName, string CounterInstance,
            System.Windows.Media.Color LineColor, LineStyles lineStyle, double lineThickness, 
            AxisTypes axis, RenderAs renderAs, string format, bool enabled, float scale)
        {
            this.axis = axis;
            this.scale   = scale;

            //-----------  Create the PerformanceCounter object  ---------------
            this.counter = new PerformanceCounter();
            this.counter.MachineName = Machine;
            this.counter.CategoryName = CounterCategory;
            this.counter.CounterName = CounterName;
            this.counter.InstanceName = CounterInstance;

            //-----------  Create the data series  -------------------------
            this.series = new DataSeries();
            this.series.RenderAs = renderAs;

            //------------  Create the summary row object  ------------------
            this.summaryRow = new CounterSummaryRow(CounterName, LineColor, lineStyle, lineThickness, format, enabled);
        }

        public void ResetStatistics()
        {
            numSamples = 0;
            sumOfSamples = 0;
            this.summaryRow.SetMax(0);
            this.summaryRow.SetMin(99999999);
            this.summaryRow.SetCurrent(0);
        }

        public void AddDataPoint()
        {
            if (!counterInvalid)
            {
                try
                {
                    float y = this.counter.NextValue() * scale;


                    //---------- Update the current, max, min and average values ---------
                    this.summaryRow.SetCurrent(y);
                    if (this.summaryRow.GetMax() < y)
                        this.summaryRow.SetMax(y);
                    if (this.summaryRow.GetMin() > y)
                        this.summaryRow.SetMin(y);

                    this.numSamples++;
                    this.sumOfSamples += y;
                    this.summaryRow.SetAvg(this.sumOfSamples / this.numSamples);

                    //----------  Add this current sample to the series for charting ---------
                    DataPoint dp = new DataPoint();
                    dp.YValue = y;
                    dp.AxisXLabel = ASLoadTest.GetTime();
                    this.series.DataPoints.Add(dp);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error processing counter [" + this.counter.CategoryName + "\\" + this.counter.CounterName + "\\" + this.counter.InstanceName + "] : " + ex.Message);
                    counterInvalid = true;
                }
            }
        }

        // the following used to calculate average
        long numSamples;
        float sumOfSamples;
        bool counterInvalid = false;
        LineStyles lineStyle { get; set; }

        public AxisTypes axis { get; set; }
        public PerformanceCounter counter { get; set; }
        public DataSeries series { get; set; }
        public CounterSummaryRow summaryRow { get; set; }
        public float scale { get; set; }
    }

    //*************************************************************************************************
    //
    //          Class: QueryThread
    //    Description: One instance of this class is created per thread during load testing.  The 
    //                 thread worker routine uses the information contained in this class to know
    //                 what work the thread is supposed to do.
    //
    //*************************************************************************************************
    class QueryThread
    {
        public QueryThread(string connString)
        {
            this.connString = connString;
            conn = new OleDbConnection(connString);
            conn.Open();
            nextQueryIndex = 0;
            isFinished = false;
            queryList = new SortedList();
        }

        public SortedList queryList { get; set; }
        public string threadName { get; set; }
        public string connString { get; set; }
        public OleDbConnection conn;
        public bool isFinished { get; set; }
        public int nextQueryIndex { get; set; }
        public BackgroundWorker WorkerThread { get; set; }
        private long totalExecutionMs = 0;
        public void addExecutionMs(long ms)
        {
            totalExecutionMs += ms;
        }
        public long TotalExecutionMs
        {
            get { return totalExecutionMs; }
        }
    }

    class QueryReportRow
    {
        public QueryReportRow(string fileName, string queryText, long durationMs, bool wasException)
        {
            this.fileName = fileName;
            this.queryText = queryText;
            addSample(durationMs, wasException);
        }

        public string fileName { get; set; }
        public string queryText { get; set; }

        private long durationTotalMs = 0;
        private long _durationMaxMs = 0;
        private long _durationMinMs = 999999999;
        private int _numExceptions = 0;
        private int numExecutions = 0;

        public void addSample(long durationMs, bool wasException)
        {
            if (wasException)
                _numExceptions++;

            if (durationMs > this._durationMaxMs)
                this._durationMaxMs = durationMs;

            if (durationMs < this._durationMinMs)
                this._durationMinMs = durationMs;

            this.durationTotalMs += durationMs;
            numExecutions++;
        }
        public long durationAvgMs
        {
            get { return (durationTotalMs / numExecutions); }
        }
        public long durationMaxMs
        {
            get { return _durationMaxMs; }
        }
        public long durationMinMs
        {
            get { return _durationMinMs; }
        }
        public int NumExecutions
        {
            get { return this.numExecutions; }
        }
        public int NumExceptions
        {
            get { return this._numExceptions; }
        }
    } // QueryReportRow class


}
